package org.juxtasoftware.dao.impl;
import java.io.IOException;
import java.io.Reader;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.List;
import org.juxtasoftware.dao.JuxtaAnnotationDao;
import org.juxtasoftware.model.AnnotationConstraint;
import org.juxtasoftware.model.JuxtaAnnotation;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.dao.support.DataAccessUtils;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.stereotype.Repository;
import eu.interedition.text.Range;
import eu.interedition.text.mem.SimpleName;
import eu.interedition.text.rdbms.RelationalName;
import eu.interedition.text.rdbms.RelationalText;
import eu.interedition.text.rdbms.RelationalTextRepository;
@Repository
public class JuxtaAnnotationDaoImpl implements JuxtaAnnotationDao, InitializingBean {
private final String tableName = "juxta_annotation";
private SimpleJdbcInsert insert;
@Autowired private JdbcTemplate jdbcTemplate;
@Override
public void afterPropertiesSet() throws Exception {
this.insert = new SimpleJdbcInsert(jdbcTemplate).withTableName(tableName).usingGeneratedKeyColumns("id");
}
@Override
public int create( final List<JuxtaAnnotation> annotations) {
if ( annotations.isEmpty() ) {
return 0;
}
StringBuilder sql = new StringBuilder();
sql.append("insert into ").append(tableName);
sql.append(" (set_id, witness_id, text_id, qname_id, range_start, range_end, manual)");
sql.append(" values (?,?,?,?,?,?,?)");
int[] rowsAffected = this.jdbcTemplate.batchUpdate(sql.toString(), new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setLong(1, annotations.get(i).getSetId());
ps.setLong(2, annotations.get(i).getWitnessId());
ps.setLong(3, ((RelationalText)annotations.get(i).getText()).getId());
ps.setLong(4, ((RelationalName)annotations.get(i).getName()).getId());
ps.setLong(5, annotations.get(i).getRange().getStart());
ps.setLong(6, annotations.get(i).getRange().getEnd());
ps.setBoolean(7, annotations.get(i).isManual());
}
@Override
public int getBatchSize() {
return annotations.size();
}
} );
return rowsAffected.length;
}
@Override
public Long create(JuxtaAnnotation annotation) {
final MapSqlParameterSource ps = new MapSqlParameterSource();
ps.addValue("set_id", annotation.getSetId());
ps.addValue("witness_id", annotation.getWitnessId());
ps.addValue("text_id", ((RelationalText)annotation.getText()).getId());
ps.addValue("qname_id", ((RelationalName)annotation.getName()).getId());
ps.addValue("range_start", annotation.getRange().getStart());
ps.addValue("range_end", annotation.getRange().getEnd());
ps.addValue("manual", annotation.isManual() );
return (Long)this.insert.executeAndReturnKey( ps );
}
@Override
public void delete(JuxtaAnnotation annotation) {
final String sql = "delete from " + this.tableName + " where id=?";
this.jdbcTemplate.update(sql, annotation.getId());
}
@Override
public long findNextTokenStart(final Long witnessId, final long fromPos) {
final String sql =
"select range_start from juxta_annotation" +
" where witness_id=? and range_start > ? order by range_start asc limit 1";
try {
long pos = this.jdbcTemplate.queryForLong(sql, witnessId, fromPos);
return pos;
} catch (Exception e) {
return -1;
}
}
@Override
public long findPriorTokenEnd(final Long witnessId, final long fromPos) {
final String sql =
"select range_end from juxta_annotation" +
" where witness_id=? and range_end < ? order by range_end desc limit 1";
try {
return this.jdbcTemplate.queryForLong(sql, witnessId, fromPos);
} catch (Exception e) {
return fromPos;
}
}
@Override
public JuxtaAnnotation find(Long id, boolean includeText) {
StringBuilder sql = new StringBuilder( getSql() );
sql.append(" where a.id = ?");
List<JuxtaAnnotation> annotations = this.jdbcTemplate.query( sql.toString(), new AnnotationMapper(), id);
Long textId = null;
if ( annotations.size() > 0 ) {
textId = ((RelationalText)annotations.get(0).getText()).getId();
}
if ( includeText ) {
readTokenContent(textId, annotations);
}
return DataAccessUtils.uniqueResult( annotations );
}
@Override
public List<JuxtaAnnotation> list( final AnnotationConstraint constraint) {
StringBuilder sql = new StringBuilder( getSql() );
sql.append(" where");
sql.append(" t.id = ? and a.set_id = ?");
List<JuxtaAnnotation> annotations = null;
if ( constraint.getFilter() != null ) {
sql.append(" and q.id in (");
sql.append( constraint.getFilter().getQNameIdListAsString() );
sql.append(")");
}
if ( constraint.getRanges().size() > 0 ) {
if ( constraint.getRanges().size() > 1 ) {
sql.append(" and (");
} else {
sql.append(" and ");
}
int cnt = 0;
for ( Range r : constraint.getRanges() ) {
if ( cnt > 0 ) {
sql.append(" or");
}
sql.append(" a.range_start >= ").append(r.getStart()).append(" and a.range_end <= ").append(r.getEnd());
cnt++;
}
if ( constraint.getRanges().size() > 1 ) {
sql.append(") order by a.range_start asc");
} else {
sql.append(" order by a.range_start asc");
}
annotations = this.jdbcTemplate.query(sql.toString(),
new AnnotationMapper(),
constraint.getTextId(), constraint.getSetId() );
} else {
sql.append(" order by a.range_start asc");
annotations = this.jdbcTemplate.query(sql.toString(),
new AnnotationMapper(),
constraint.getTextId(), constraint.getSetId() );
}
// pull token content for all from the witness text
if ( constraint.isIncludeText() ) {
readTokenContent( constraint.getTextId(), annotations );
}
return annotations;
}
private void readTokenContent(Long textId, List<JuxtaAnnotation> annotations) {
try {
if ( annotations.size() == 0) {
return;
}
// get a reader for the text content associated with the annotations
final String sql = "select content from text_content where id=?";
Reader txtReader = DataAccessUtils.uniqueResult(this.jdbcTemplate.query(sql, new RowMapper<Reader>() {
@Override
public Reader mapRow(ResultSet rs, int rowNum) throws SQLException {
return rs.getCharacterStream("content");
}
}, textId));
// pick the first non-gap annotation
Iterator<JuxtaAnnotation> itr = annotations.iterator();
JuxtaAnnotation currAnno = null;
while (itr.hasNext()) {
JuxtaAnnotation a = itr.next();
if ( a.getRange().length() > 0 ) {
currAnno= a;
break;
}
}
if ( currAnno == null) {
// all gaps? nothin to do
return;
}
// fill in the content for all non-gap annotations
int pos = 0;
StringBuilder currToken = new StringBuilder();
while (true ) {
int data = txtReader.read();
if ( data == -1 ) {
currAnno.setContent( currToken.toString() );
break;
} else {
if ( pos >= currAnno.getRange().getStart() && pos < currAnno.getRange().getEnd()) {
currToken.append( (char)data );
if ( (pos+1) == currAnno.getRange().getEnd() ) {
//System.err.println("["+currToken.toString()+"] len " +currToken.length());
currAnno.setContent( currToken.toString() );
currAnno = null;
while ( itr.hasNext() ) {
JuxtaAnnotation test = itr.next();
if ( test.getRange().length() > 0) {
currAnno = test;
currToken = new StringBuilder();
break;
}
}
if ( currAnno == null ) {
break;
}
}
}
pos++;
}
}
} catch (IOException e ) {
throw new DataAccessResourceFailureException("Unable to retrieve annotation content", e);
}
}
private String getSql() {
StringBuilder sql = new StringBuilder();
sql.append("select a.id, a.set_id, a.witness_id, a.text_id, a.qname_id, a.range_start, a.range_end, ");
sql.append(RelationalTextRepository.selectTextFrom("t"));
sql.append(", q.id, q.local_name, q.namespace ");
sql.append(" from ").append(this.tableName).append(" as a");
sql.append(" join text_content t on a.text_id = t.id");
sql.append(" join text_qname q on a.qname_id = q.id");
return sql.toString();
}
/**
* Map annotation db to model
* @author loufoster
*/
private static class AnnotationMapper implements RowMapper<JuxtaAnnotation> {
public JuxtaAnnotation mapRow(ResultSet rs, int rowNum) throws SQLException {
final JuxtaAnnotation annotation = new JuxtaAnnotation(
rs.getLong("id"),
rs.getLong("set_id"),
rs.getLong("witness_id"),
RelationalTextRepository.mapTextFrom(rs, "t"),
new SimpleName(rs.getString("namespace"), rs.getString("local_name")),
new Range(rs.getInt("range_start"), rs.getInt("range_end"))
);
return annotation;
}
}
}